package com.lion.httpapi.handler;

import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.QueryStringDecoder;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.lion.httpapi.HttpServer;
import com.lion.httpapi.config.Configuration;
import com.lion.httpapi.invok.InvokThread;
import com.lion.httpapi.servlet.ApiRequest;
import com.lion.httpapi.servlet.ApiResponse;
import com.lion.httpapi.thread.ThreadManager;
import com.lion.httpapi.thread.ThreadPool;
import com.lion.httpapi.thread.ThreadPoolType;
import com.lion.httpapi.tools.JsonTools;

public class HttpServerInboundHandler extends SimpleChannelInboundHandler<Object> {

    private static Log log = LogFactory.getLog(HttpServerInboundHandler.class);
    private Configuration conf = Configuration.getInstance();
	private ThreadPool threadPool = ThreadManager.getThreadManager().getThreadPool(ThreadPoolType.HTTP_API, conf.getInt("server.max.thread",100));
    private HttpRequest request;
    private String contentType;
    private Charset charset = Charset.forName("UTF-8");
    private String uri;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        if (msg instanceof HttpRequest) {
            request = (HttpRequest) msg;
            uri = request.uri();
            contentType = request.headers().get("Content-Type");
            uri = URLDecoder.decode(uri, charset.name());
        }
        if (msg instanceof HttpContent) {
        	ApiResponse nettyResponse = null;
            HttpContent content = (HttpContent) msg;
            ByteBuf buf = content.content();
            String postData = buf.toString(charset);
            
            String clientName = request.headers().get("clientName");
            String timeout = request.headers().get("timeOut");
            if(timeout == null){
            	timeout = "60000";
            }
            if(clientName == null){
            	clientName = request.headers().get("User-Agent");
            }
            try{
            	String data = "";
            	String getData = "";
            	String[] uriArr = uri.split("\\?");
            	if(uriArr.length>1){
            		getData = uriArr[1];
            	}
            	uri = uriArr[0];
            	HttpMethod httpMethod = request.method();
            	Method m = HttpServer.findMethodByPath(httpMethod.name()+":"+uri);
            	if(httpMethod == HttpMethod.GET){
            		QueryStringDecoder decoder = new QueryStringDecoder(request.uri());  
                    data = JsonTools.beanToJson(decoder.parameters());
            	}else if(httpMethod == HttpMethod.POST){
            		if(postData!=null&&postData.trim().length()>0){
            			Hashtable<String,Object> postDataMap = new Hashtable<String,Object>();
            			if(contentType.contains("multipart/form-data")){
            				postDataMap = parseFromdata(postData,getData);
            				data = JsonTools.beanToJson(postDataMap);
            			}else if(contentType.contains("x-www-form-urlencoded")){
            				postDataMap = parseQueryString(postData+"&"+getData);
            				data = JsonTools.beanToJson(postDataMap);
            			}else {
            				data = postData;
            			}
            		}
            	}
            	ApiRequest nettyRequest = new ApiRequest(uri, clientName, data, Long.valueOf(timeout));
				if(m == null){
					if(httpMethod == HttpMethod.GET){
						m = HttpServer.findMethodByPath(HttpMethod.POST.name()+":"+uri);
					}else if(httpMethod == HttpMethod.POST){
						m = HttpServer.findMethodByPath(HttpMethod.GET.name()+":"+uri);
					}
					if(m == null){
						nettyResponse = new ApiResponse(uri, "找不到指定的路径:"+uri, HttpResponseStatus.NOT_FOUND.code());
					}else{
						nettyResponse = new ApiResponse(uri, "错误的请求方式:"+httpMethod, HttpResponseStatus.METHOD_NOT_ALLOWED.code());
					}
				}else{
					String className = m.getDeclaringClass().getName();
					Object o = HttpServer.findObjectByClassName(className);
					nettyResponse = new ApiResponse(uri, "OK", HttpResponseStatus.OK.code());
					doInvok(o,m,nettyRequest,nettyResponse);
				}
			}catch(Exception e){
				e.printStackTrace();
				StringBuilder sb = new StringBuilder();
				sb.append(e.toString()).append("\n");
				for(StackTraceElement stc : e.getStackTrace()){
					sb.append(stc.toString()).append("\n");
				}
				nettyResponse = new ApiResponse(uri, "系统错误", HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), sb.toString());
			}
            writeResponse(ctx, nettyResponse);
        }
    }
    
    private Hashtable<String, Object> parseFromdata(String s, String getData){
    	Hashtable<String, Object> ht = new Hashtable<String, Object>();
    	if(getData!=null){
    		Hashtable<String, Object> get = parseQueryString(getData);
    		if (get != null && !get.isEmpty()) {
    			ht.putAll(get);
    		}
    	}
    	String[] formDataArr = s.split("\r\n");
		if(s.contains("Content-Disposition: form-data;")){
			String key = formDataArr[0];
			int status = 0;
			StringBuilder name = new StringBuilder();
			StringBuilder value = new StringBuilder();
			for(String line : formDataArr){
				if(line.contains(key)){
					status = 0;
				}
				if(line.trim().length()==0){
					status = 1;
					continue;
				}
				if(line.contains("Content-Disposition: form-data;")){
					status = 2;
				}
				switch (status) {
					case 0:
						if(name.length()>0){
							ht.put(name.toString(), value.toString());
						}
						name.delete(0, name.length());
						value.delete(0, value.length());
						break;
					case 1:
						value.append(line);
						break;
					case 2:
						line = line.replace("Content-Disposition: form-data; name=\"", "");
						name.append(line.substring(0,line.length()-1));
						break;
					case -1:
					default:
						break;
				}
			}
		}
		return ht;
    }
    
	private Hashtable<String, Object> parseQueryString(String s) {
		String valArray[] = null;
		if (s == null) {
			throw new IllegalArgumentException();
		}
		Hashtable<String, Object> ht = new Hashtable<String, Object>();
		StringBuffer sb = new StringBuffer();
		StringTokenizer st = new StringTokenizer(s, "&");
		while (st.hasMoreTokens()) {
			String pair = (String) st.nextToken();
			int pos = pair.indexOf('=');
			if (pos == -1) {
				throw new IllegalArgumentException();
			}
			String key = parseName(pair.substring(0, pos), sb);
			String val = parseName(pair.substring(pos + 1, pair.length()), sb);
			if (ht.containsKey(key)) {
				String oldVals[] = null;
				if(ht.get(key) instanceof String){
					oldVals = new String[]{(String) ht.get(key)};
				}else{
					oldVals = (String[]) ht.get(key);
				}
				valArray = new String[oldVals.length + 1];
				for (int i = 0; i < oldVals.length; i++)
					valArray[i] = oldVals[i];
				valArray[oldVals.length] = val;
			} else {
				valArray = new String[1];
				valArray[0] = val;
			}
			if(valArray.length==1){
				ht.put(key, valArray[0]);
			}else{
				ht.put(key, valArray);
			}
		}
		return ht;
	}

	private String parseName(String s, StringBuffer sb) {
		sb.setLength(0);
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);
			switch (c) {
			case '+':
				sb.append(' ');
				break;
			case '%':
				try {
					sb.append((char) Integer.parseInt(
							s.substring(i + 1, i + 3), 16));
					i += 2;
				} catch (NumberFormatException e) {
					throw new IllegalArgumentException();
				} catch (StringIndexOutOfBoundsException e) {
					String rest = s.substring(i);
					sb.append(rest);
					if (rest.length() == 2)
						i++;
				}
				break;
			default:
				sb.append(c);
				break;
			}
		}
		return sb.toString();
	}
	
    private void writeResponse(ChannelHandlerContext ctx,ApiResponse nettyResponse) throws UnsupportedEncodingException{
    	String res = JsonTools.beanToJson(nettyResponse);
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
        		HttpResponseStatus.valueOf(nettyResponse.getCode()), Unpooled.wrappedBuffer(res.getBytes(charset.name())));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/json; charset="+charset.name());
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH,
                response.content().readableBytes());
        if (HttpUtil.isKeepAlive(request)) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
    
	private void doInvok(Object o, Method m, ApiRequest request, ApiResponse nettyResponse) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		CountDownLatch cdl = new CountDownLatch(1);
		InvokThread thread = new InvokThread(o, m, request, nettyResponse, cdl);
		boolean addFlag = threadPool.execute(thread);
		if(addFlag){
			try {
				boolean flag = cdl.await(request.getTimeout(),TimeUnit.MILLISECONDS);
				if(!flag){
					nettyResponse.setCode(HttpResponseStatus.GATEWAY_TIMEOUT.code());
					nettyResponse.setMessage("GATEWAY_TIMEOUT");
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}else{
			nettyResponse.setCode(HttpResponseStatus.FORBIDDEN.code());
			nettyResponse.setMessage("SERVICE_IS_BUSY");
		}
	}
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    	StringBuilder sb = new StringBuilder(cause.toString());
    	for(StackTraceElement s : cause.getStackTrace()){
    		sb.append("\n\t").append(s);
    	}
    	log.error(sb.toString());
        ctx.close();
    }

}